package com.mjf.linkedlist;

import java.util.Stack;

/**
 * 使用带head头的单向链表实现 –水浒英雄排行榜管理
 * 完成对英雄人物的增删改查操作
 * （1）在添加英雄时，直接添加到链表的尾部		方法：add
 * （2）在添加英雄时，根据排名将英雄插入到指定位置(如果有这个排名，则添加失败，并给出提示)	方法：addByOrder
 * <p>
 * 面试题：
 * （1）求单链表中有效节点的个数		方法：SingleLinkedList.getLength()
 * （2）查找单链表中的倒数第k个结点 【新浪面试题】	方法：findLastNode
 * （3）单链表的反转【腾讯面试题，有点难度】		方法：reverseList
 * （4）从尾到头打印单链表 【百度】 方式1：反向遍历(不建议) 方式2：Stack栈	方法：reversePrint
 */
public class SingleLinkedListDemo {

    public static void main(String[] args) {

        // 进行测试
        // 先创建节点
        HeroNode hero1 = new HeroNode(1, "宋江", "及时雨");
        HeroNode hero2 = new HeroNode(2, "卢俊义", "玉麒麟");
        HeroNode hero3 = new HeroNode(3, "吴用", "智多星");
        HeroNode hero4 = new HeroNode(4, "林冲", "豹子头");

        // 创建一个链表
        SingleLinkedList singleLinkedList = new SingleLinkedList();
        // 加入
//		singleLinkedList.add(hero1);
//		singleLinkedList.add(hero2);
//		singleLinkedList.add(hero3);
//		singleLinkedList.add(hero4);

        // 加入按照编号的顺序
        singleLinkedList.addByOrder(hero4);
        singleLinkedList.addByOrder(hero2);
        singleLinkedList.addByOrder(hero1);
        singleLinkedList.addByOrder(hero3);
        singleLinkedList.addByOrder(hero3);

        // 显示链表
        singleLinkedList.list();

        // 修改编号为2的信息
        singleLinkedList.update(new HeroNode(2, "小卢", "-玉麒麟-"));
        // 显示链表
        System.out.println("修改后的链表情况");
        singleLinkedList.list();

        // 删除一个节点
        singleLinkedList.delete(2);
        // 显示链表
        System.out.println("删除后的链表情况");
        singleLinkedList.list();

        // 测试 求单链表中有效节点的个数
        System.out.println("有效节点的个数：" + SingleLinkedList.getLength(singleLinkedList.getHead()));

        // 测试 找倒数第k个节点
        System.out.println(SingleLinkedList.findLastNode(singleLinkedList.getHead(), 2));

        // 测试 单链表反转
        SingleLinkedList.reverseList(singleLinkedList.getHead());
        System.out.println("反转后的单链表");
        singleLinkedList.list();

        // 测试 单链表逆序打印
        System.out.println("逆序打印的单链表");
        SingleLinkedList.reversePrint(singleLinkedList.getHead());
        System.out.println("单链表本身的结构");
        singleLinkedList.list();

    }

}

/**
 * 定义单链表来管理英雄
 */
class SingleLinkedList {

    /**
     * 先初始化一个头节点，不存放具体数据
     */
    private final HeroNode head = new HeroNode(0, "", "");

    /**
     * 返回头节点
     */
    public HeroNode getHead() {
        return head;
    }


    /**
     * 将单链表逆序打印【百度面试题】
     * 思路：可以利用栈这个数据结构，将各个节点压入到栈中，然后利用栈的先进后出的特点来实现
     */
    public static void reversePrint(HeroNode head) {
        if (head.next == null) {
            return;        // 空链表
        }

        // 创建一个栈，将各个节点压入栈中
        Stack<HeroNode> stack = new Stack<>();
        HeroNode cur = head.next;

        while (cur != null) {
            stack.add(cur);        // 入栈
            cur = cur.next;
        }

        while (stack.size() > 0) {
            System.out.println(stack.pop());    // 出栈
        }

    }


    /**
     * 将单链表反转【腾讯面试题】
     * <p>
     * 1.遍历原来的链表
     * 2.每遍历一个节点，就将其取出，并放在新的链表reverseHead的最前端
     */
    public static void reverseList(HeroNode head) {

        // 如果当前链表为空，或者只有一个节点，无需反转，直接返回
        if (head.next == null || head.next.next == null) {
            return;
        }

        // 定义一个辅助变量，帮助我们遍历原来的链表
        HeroNode cur = head.next;
        HeroNode next;    // 指向当前节点[cur]的下一个节点
        HeroNode reverseHead = new HeroNode(0, "", "");

        // 遍历原来的链表
        // 每遍历一个节点，就将其取出，并放在新的链表reverseHead的最前端
        while (cur != null) {
            next = cur.next;    // 先暂时保存当前节点的下一个节点，因为后面需要使用
            cur.next = reverseHead.next;    // 将cur的下一个节点指向新的链表的最前端
            reverseHead.next = cur;        // 将cur连接到新的链表上
            cur = next;    // cur后移
        }

        // 将head.next指向reverseHead.next，实现单链表的反转
        head.next = reverseHead.next;

    }

    /**
     * 查找单链表中倒数第k个节点【新浪面试题】
     * 思路：
     * 1.编写一个方法，接收head节点，同时接收一个index
     * 2.index表示倒数第index个节点
     * 3.先把链表从头到尾遍历，得到链表的总的长度 getLength()
     * 4.得到size后，我们从链表的第一个开始遍历(size-length)个，就可以得到
     * 5.如果找到了，则返回该节点，否则返回null
     */
    public static HeroNode findLastNode(HeroNode head, int index) {

        // 如果链表为空，返回null
        if (head.next == null) {
            return null;
        }

        // 第一次遍历得到链表的长度(节点个数)
        int size = getLength(head);
        // 第二次遍历 size-index 位置，就是倒数的第k个节点
        // 先做一个index的校验
        if (index <= 0 || index > size) {
            return null;
        }

        // 第一个辅助变量
        HeroNode cur = head.next;

        for (int i = 0; i < (size - index); i++) {
            cur = cur.next;
        }

        return cur;

    }

    /**
     * 面试题1：
     * 获取单链表有效节点个数（如果是带有头结点的链表，需要不统计头节点）
     *
     * @param head 链表的头节点
     * @return 返回的就是有效节点的个数
     */
    public static int getLength(HeroNode head) {

        if (head.next == null) {        // 空链表
            return 0;
        }

        int length = 0;
        // 定义一个辅助变量，这里我们没有统计头节点
        HeroNode cur = head.next;
        while (cur != null) {
            length++;
            cur = cur.next;    // 遍历
        }

        return length;

    }

    /**
     * 添加节点到单向列表，直接添加到链表尾部
     * 思路：当不考虑编号的顺序时
     * 1.找到当前链表的最后节点
     * 2.将最后节点的next指向新的节点
     *
     * @param heroNode 待添加的节点
     */
    public void add(HeroNode heroNode) {

        // 因为head节点不能动，因此我们需要一个辅助变量temp来遍历
        HeroNode temp = head;

        // 遍历链表，找到最后
        while (temp.next != null) { // 说明已经找到
            // 如果没有找到，就将temp后移
            temp = temp.next;
        }

        // 当退出while循环时，temp就指向了链表的最后
        // 将最后这个节点
        temp.next = heroNode;

    }

    /**
     * 在添加英雄时，根据排名将英雄插入到指定位置
     * 如果有这个排名，则添加失败，并给出提示
     *
     * @param heroNode 待添加的节点
     */
    public void addByOrder(HeroNode heroNode) {

        // 因为head节点不能动，因此我们需要一个辅助变量temp来帮助我们找到添加的位置
        // 因为单链表，因此我们找的temp是位于添加位置的前一个节点，否则添加不了
        HeroNode temp = head;
        boolean flag = false;    // 标志添加的编号是否存在，默认为false

        while (true) {
            if (temp.next == null) {        // 说明temp已经在链表的最后
                break;
            }

            if (temp.next.no > heroNode.no) {    // 位置找到，就在temp的后面插入
                break;
            } else if (temp.next.no == heroNode.no) {    // 说明希望添加的heroNode的编号存在
                flag = true;    // 说明编号存在
                break;
            }

            temp = temp.next;    // 后移，遍历当前链表
        }

        // 判断flag的值
        if (flag) {
            System.out.printf("准备插入的英雄的编号%d已经存在，不能添加\n", heroNode.no);
        } else {
            // 插入到链表中temp后面
            heroNode.next = temp.next;
            temp.next = heroNode;
        }

    }

    /**
     * 删除节点
     * <p>
     * 思路:
     * 1.head节点不能动，因此我们需要一个temp辅助节点找到待删除节点的前一个节点
     * 2.说明我们在比较时，是temp.next.no和需要删除的节点的no比较
     *
     * @param no 待删除的节点ID
     */
    public void delete(int no) {
        HeroNode temp = head;
        boolean flag = false;    // 标志是否找到待删除节点的前一个节点
        while (true) {
            if (temp.next == null) {        // 已经到链表的最后
                break;
            }

            if (temp.next.no == no) {
                // 找到的待删除节点的前一个节点temp
                flag = true;
                break;
            }
            temp = temp.next;    // 节点后移
        }

        if (flag) {    // 找到
            // 可以删除
            temp.next = temp.next.next;
        } else {
            System.out.printf("要删除的%d节点不存在\n", no);
        }
    }

    /**
     * 修改节点的信息，根据no来修改，即no不能修改
     * 说明：根据newHeroNode的no来修改即可
     *
     * @param newHeroNode 新节点
     */
    public void update(HeroNode newHeroNode) {
        // 判断是否为空
        if (head.next == null) {
            System.out.println("链表为空");
            return;
        }
        // 找到需要修改的节点，根据no编号
        // 定义一个辅助变量
        HeroNode temp = head.next;
        boolean flag = false;    // 标志是否找到该节点
        while (true) {
            if (temp == null) {
                break;    // 已经遍历完链表
            }

            if (temp.no == newHeroNode.no) {        // 找到
                flag = true;
                break;
            }

            temp = temp.next;
        }

        // 根据flag判断是否找到要修改的节点
        if (flag) {
            temp.name = newHeroNode.name;
            temp.nickname = newHeroNode.nickname;
        } else {
            System.out.printf("没有找到编号%d的节点\n", newHeroNode.no);
        }
    }

    /**
     * 显示链表[遍历]
     */
    public void list() {
        // 判断链表是否为空
        if (head.next == null) {
            System.out.println("链表为空");
            return;
        }

        // 因为头节点不能动，因此需要一个辅助变量来遍历
        HeroNode temp = head.next;
        while (temp != null) {
            // 判断是否到链表最后
            // 输出节点的信息
            System.out.println(temp);
            // 将temp后移
            temp = temp.next;
        }
    }


}

/**
 * 定义一个HeroNode,每个HeroNode对象就是一个节点
 */
class HeroNode {

    public int no;
    public String name;
    public String nickname;
    /**
     * 指向下一个节点
     */
    public HeroNode next;

    public HeroNode(int hNo, String hName, String hNickname) {
        this.no = hNo;
        this.name = hName;
        this.nickname = hNickname;
    }

    @Override
    public String toString() {
        return "HeroNode [no=" + no + ", name=" + name + ", nickname=" + nickname + "]";
    }


}